package jsyntaxpane.util.intellisense.bsh;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Classe que cuida de gerenciar imports e atalhos para classes. Pode receber um "parent" para reaproveitar escopos
 * 
 * @author takeshi
 */
public class ImportsCalculator {

    private final Map<String, Class<?>> imports = new ConcurrentHashMap<String, Class<?>>();

    private final List<String> wildcardImports = Collections.synchronizedList(new LinkedList<String>());

    private final ImportsCalculator parent;

    private static final ImportsCalculator DEFAULT_PARENT = new ImportsCalculator() {
        @Override
        public Class<?> resolveTypeToken(String arg0, Class<?> arg1) {
            return arg1;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Collection<String> getImportedNames() {
            return Collections.emptyList();
        }

    };

    /**
     * 
     */
    public ImportsCalculator() {
        this(DEFAULT_PARENT);
    }

    /**
     * @param parent {@link ImportsCalculator}
     */
    public ImportsCalculator(ImportsCalculator parent) {
        this.parent = parent;
    }

    /**
     * Adiciona um import
     * 
     * @param imp {@link String}
     */
    public void addImport(String imp) {
        if (imp == null) {
            return;
        }
        if (imp.endsWith(";")) {
            addImport(imp.substring(0, imp.length() - 1));
            return;
        }
        if (imp.startsWith("import ")) {
            addImport(imp.substring("import ".length()));
            return;
        }
        String toAdd = imp.trim();
        if (toAdd.endsWith("*")) {
            wildcardImports.add(toAdd.substring(0, toAdd.length() - 1));
        } else {
            int dot = toAdd.lastIndexOf('.');
            final String alias;
            if (dot >= 0) {
                alias = toAdd.substring(dot + 1);
            } else {
                alias = toAdd;
            }
            final Class<?> type = typeForClassName(toAdd);
            imports.put(alias, type == null ? Object.class : type);
        }
    }

    /**
     * Devolve uma classe dado o seu FQCN
     * 
     * @param fqcn {@link String}
     * @return {@link Class}
     */
    public static Class<?> typeForClassName(String fqcn) {
        Class<?> t0 = typeForClassName(fqcn, BshAutocompleteTool.class.getClassLoader());
        if (t0 == null) {
            return typeForClassName(fqcn, Thread.currentThread().getContextClassLoader());
        }
        return t0;
    }

    /**
     * Devolve a classe para um classloader
     * 
     * @param fqcn {@link String}
     * @param cl {@link ClassLoader}
     * @return {@link Class}
     */
    public static Class<?> typeForClassName(String fqcn, ClassLoader cl) {
        try {
            return Class.forName(fqcn, false, cl);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    /**
     * Devolve os nomes dos imports
     * 
     * @return Collection de String
     */
    public Collection<String> getImportedNames() {
        final Set<String> set = new HashSet<String>(this.imports.keySet());
        set.addAll(parent.getImportedNames());
        return set;
    }

    /**
     * @param typeToken the type token
     * @param defType the default type
     * @return Class<?>
     */
    public Class<?> resolveTypeToken(String typeToken, Class<?> defType) {
        Class<?> cached = this.imports.get(typeToken);
        if (cached != null) {
            return cached;
        }
        Class<?> t0 = typeForClassName(typeToken);
        if (t0 != null) {
            imports.put(typeToken, t0);
            return t0;
        }
        for (String wild : wildcardImports) {
            Class<?> t = typeForClassName(wild + typeToken);
            if (t != null) {
                imports.put(typeToken, t);
                return t;
            }
        }
        return this.parent.resolveTypeToken(typeToken, defType);
    }
}
